/*----------------------------------------------------------------------*
 * IFFW.C  Support routines for writing IFF-85 files.          1/23/86
 * (IFF is Interchange Format File.)
 *
 * By Jerry Morrison and Steve Shaw, Electronic Arts.
 *
 * This version is not any longer for the Commodore-Amiga computer.
 *----------------------------------------------------------------------*/
#include "basiciff.h"
#include <arpa/inet.h>

/* ---------- IFF Writer -----------------------------------------------*/

/* A macro to test if a chunk size is definite, i.e. not szNotYetKnown.*/
#define Known(size)   ( (size) != szNotYetKnown )

/* Yet another weird macro to make the source code simpler...*/
#define IfIffp(expr)  {if (iffp == IFF_OKAY)  iffp = (expr);}

/* ---------- OpenWIFF -------------------------------------------------*/

IFFP OpenWIFF(FILE *file, GroupContext *new, int32_t limit) {
  register IFFP iffp = IFF_OKAY;

  new->parent       = NULL;
  new->clientFrame  = NULL;
  new->file         = file;
  new->position     = 0;
  new->bound        = limit;
  new->ckHdr.ckID   = NULL_CHUNK;  /* indicates no current chunk */
  new->ckHdr.ckSize = new->bytesSoFar = 0;

  if (0 > fseek(file, 0, SEEK_SET))	/* Go to start of the file.*/
    iffp = DOS_ERROR;
  else if ( Known(limit) && IS_ODD(limit) )
    iffp = CLIENT_ERROR;
  return(iffp);
}

/* ---------- StartWGroup ----------------------------------------------*/
IFFP StartWGroup(GroupContext *parent, ID groupType, int32_t groupSize, ID subtype, GroupContext *new) {
  register IFFP iffp;
  
  iffp = PutCkHdr(parent, groupType, groupSize);
  IfIffp( IFFWriteInt32(parent, subtype) );
  IfIffp( OpenWGroup(parent, new) );
  return(iffp);
}

/* ---------- OpenWGroup -----------------------------------------------*/
IFFP OpenWGroup(GroupContext *parent, GroupContext *new) {
  register int32_t ckEnd;
  register IFFP iffp = IFF_OKAY;
  
  new->parent       = parent;
  new->clientFrame  = parent->clientFrame;
  new->file         = parent->file;
  new->position     = parent->position;
  new->bound        = parent->bound;
  new->ckHdr.ckID   = NULL_CHUNK;
  new->ckHdr.ckSize = new->bytesSoFar = 0;

  if ( Known(parent->ckHdr.ckSize) ) {
    ckEnd = new->position + ChunkMoreBytes(parent);
    if ( new->bound == szNotYetKnown || new->bound > ckEnd )
      new->bound = ckEnd;
  };

  if ( parent->ckHdr.ckID == NULL_CHUNK || /* not currently writing a chunk*/
       IS_ODD(new->position) ||
       (Known(new->bound) && IS_ODD(new->bound)) )
    iffp = CLIENT_ERROR;
  return(iffp);
}

/* ---------- CloseWGroup ----------------------------------------------*/
IFFP CloseWGroup(GroupContext *old) {
  IFFP iffp = IFF_OKAY;

  if ( old->ckHdr.ckID != NULL_CHUNK )  /* didn't close the last chunk */
    iffp = CLIENT_ERROR;
  else if ( old->parent == NULL ) {	  /* top level file context */
    if (fflush(old->file) < 0) iffp = DOS_ERROR;
  }
  else {				  /* update parent context */
    old->parent->bytesSoFar += old->position - old->parent->position;
    old->parent->position = old->position;
  };
  return(iffp);
}

/* ---------- EndWGroup ------------------------------------------------*/
IFFP EndWGroup(GroupContext *old) {
  register GroupContext *parent = old->parent;
  register IFFP iffp;

  if(parent == NULL) return CLIENT_ERROR;
  iffp = CloseWGroup(old);
  IfIffp( PutCkEnd(parent) );
  return(iffp);
}

/* ---------- PutCk ----------------------------------------------------*/
IFFP PutCk(GroupContext *context, ID ckID, int32_t ckSize, void *data) {
  register IFFP iffp = IFF_OKAY;
  
  if ( ckSize == szNotYetKnown )
    iffp = CLIENT_ERROR;
  IfIffp( PutCkHdr(context, ckID, ckSize) );
  IfIffp( IFFWriteBytes(context, data, ckSize) );
  IfIffp( PutCkEnd(context) );
  return(iffp);
}

/* ---------- PutCkHdr -------------------------------------------------*/
IFFP PutCkHdr(GroupContext *context, ID ckID, int32_t ckSize) {
  int32_t endianess;
  int32_t minPSize = sizeof(ChunkHeader); /* physical chunk >= minPSize bytes*/
  
  /* CLIENT_ERROR if we're already inside a chunk or asked to write
   * other than one FORM, LIST, or CAT at the top level of a file */
  /* Also, non-positive ID values are illegal and used for error codes.*/
  /* (We could check for other illegal IDs...)*/
  if ( context->ckHdr.ckID != NULL_CHUNK  ||  ckID <= 0 )
    return(CLIENT_ERROR);
  else if (context->parent == NULL)  {
    switch (ckID)  {
    case FORM:
    case LIST:
    case CAT:
      break;
    default:
      return(CLIENT_ERROR);
    }
    if (context->position != 0)
      return(CLIENT_ERROR);
  }
  
  if ( Known(ckSize) ) {
    if ( ckSize < 0 )
      return(CLIENT_ERROR);
    minPSize += ckSize;
  }
  if ( Known(context->bound)  &&
       context->position + minPSize > context->bound )
    return(CLIENT_ERROR);
  
  context->ckHdr.ckID   = ckID;
  context->ckHdr.ckSize = ckSize;
  context->bytesSoFar   = 0;
  /* We can not write the context out easily as we must take care of
     endianess. Therefore we copy the id and the length to a int32_t
     variable and write this one...  It would be very nice if
     everybody would crack their eggs on the right end.
  */
  endianess = htonl(context->ckHdr.ckID);
  if (0 > fwrite((void *)&endianess, sizeof(endianess), 1, context->file))
    return(DOS_ERROR);
  endianess = htonl(context->ckHdr.ckSize);
  if (0 > fwrite((void *)&endianess, sizeof(endianess), 1, context->file))
    return(DOS_ERROR);
  context->position += sizeof(ChunkHeader);
  return(IFF_OKAY);
}

/* ---------- IFFWriteBytes ---------------------------------------------*/
IFFP IFFWriteBytes(GroupContext *context, void *data, int32_t nBytes) {
  if ( context->ckHdr.ckID == NULL_CHUNK  ||	/* not in a chunk */
       nBytes < 0  ||				/* negative nBytes */
       (Known(context->bound)  &&		/* overflow context */
	context->position + nBytes > context->bound)  ||
       (Known(context->ckHdr.ckSize)  &&   	/* overflow chunk */
	context->bytesSoFar + nBytes > context->ckHdr.ckSize) )
    return(CLIENT_ERROR);
  
  if (0 > fwrite(data, 1, nBytes, context->file))
    return(DOS_ERROR);
  
  context->bytesSoFar += nBytes;
  context->position   += nBytes;
  return(IFF_OKAY);
}

/* ---------- IFFWriteInt32 ---------------------------------------------*/
/* \brief Write an int32_t to a iff file.
 *
 * Again we have to check for endianess issues, so we just write using
 * this function.
 *
 * \param context group context pointer, can not be the whole file
 * \param data the int32_t value to write
 * \return IFF_OKAY on success
 */
IFFP IFFWriteInt32(GroupContext *context, int32_t data) {
  int32_t endianess = htonl(data);
  int32_t nBytes = sizeof(endianess);

  if ( context->ckHdr.ckID == NULL_CHUNK  ||	/* not in a chunk */
       nBytes < 0  ||				/* negative nBytes */
       (Known(context->bound)  &&		/* overflow context */
	context->position + nBytes > context->bound)  ||
       (Known(context->ckHdr.ckSize)  &&   	/* overflow chunk */
	context->bytesSoFar + nBytes > context->ckHdr.ckSize) )
    return(CLIENT_ERROR);
  
  if (0 > fwrite(&endianess, 1, nBytes, context->file))
    return(DOS_ERROR);
  
  context->bytesSoFar += nBytes;
  context->position   += nBytes;
  return(IFF_OKAY);
}


/* ---------- PutCkEnd -------------------------------------------------*/
IFFP PutCkEnd(GroupContext *context) {
  int32_t endianess;
  int16_t zero = 0;	/* padding source */

  if ( context->ckHdr.ckID == NULL_CHUNK )  /* not in a chunk */
    return(CLIENT_ERROR);
  
  if ( context->ckHdr.ckSize == szNotYetKnown ) {
    /* go back and set the chunk size to bytesSoFar */
    /* but remember to take care of endianess issues. */
    endianess = htonl(context->bytesSoFar);
    if ( 0 >
	 fseek(context->file, -(context->bytesSoFar + sizeof(int32_t)), SEEK_CUR) ||
	 0 >
	 fwrite((void *)&endianess, sizeof(int32_t), 1, context->file)  ||
	 0 >
	 fseek(context->file, context->bytesSoFar, SEEK_CUR)  )
      return(DOS_ERROR);
  }
  else {  /* make sure the client wrote as many bytes as planned */
    if ( context->ckHdr.ckSize != context->bytesSoFar )
      return(CLIENT_ERROR);
  };
  
  /* Write a pad byte if needed to bring us up to an even boundary.
   * Since the context end must be even, and since we haven't
   * overwritten the context, if we're on an odd position there must
   * be room for a pad byte. */
  if ( IS_ODD(context->bytesSoFar) ) {
    if ( 0 > fwrite((void *)&zero, 1, 1, context->file) )
      return(DOS_ERROR);
    context->position += 1;
  };
  
  context->ckHdr.ckID   = NULL_CHUNK;
  context->ckHdr.ckSize = context->bytesSoFar = 0;
  return(IFF_OKAY);
}

